frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Tooltip from '@mui/material/Tooltip';
3import IconButton from '@mui/material/IconButton';
4import Box from '@mui/material/Box';
5import Link from '@mui/material/Link';
6import Card from '@mui/material/Card';
7import Container from '@mui/material/Container';
8import TextField from '@mui/material/TextField';
9import Typography from '@mui/material/Typography';
10import TuneIcon from '@mui/icons-material/Tune';
11import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
12import {useTheme} from '@mui/material/styles';
13import {DatePicker} from '@mui/x-date-pickers/DatePicker';
14import {PropsWithChildren, useState} from 'react';
15import {useTranslation} from 'react-i18next';
16import pageUtils from '../../../lib/pageUtils';
17import ShareEvent from '../../../containers/ShareEvent';
18import PlaceInput from '../../../containers/PlaceInput';
19import LangSelector from '../../../components/LangSelector';
20import usePermissions from '../../../hooks/usePermissions';
21import useEventStore from '../../../stores/useEventStore';
22import useToastStore from '../../../stores/useToastStore';
23import EventLayout, {TabComponent} from '../../../layouts/Event';
24import {
25 EventByUuidDocument,
26 useUpdateEventMutation,
27} from '../../../generated/graphql';
28
29interface Props {
30 eventUUID: string;
31 announcement?: string;
32}
33
34const Page = (props: PropsWithChildren<Props>) => {
35 return <EventLayout {...props} Tab={DetailsTab} />;
36};
37
38const DetailsTab: TabComponent<Props> = ({}) => {
39 const {t} = useTranslation();
40 const {
41 userPermissions: {canEditEventDetails},
42 } = usePermissions();
43 const theme = useTheme();
44 const [updateEvent] = useUpdateEventMutation();
45 const addToast = useToastStore(s => s.addToast);
46 const setEventUpdate = useEventStore(s => s.setEventUpdate);
47 const event = useEventStore(s => s.event);
48 const [isEditing, setIsEditing] = useState(false);
49
50 if (!event) return null;
51
52 const onSave = async e => {
53 try {
54 const {uuid, ...data} = event;
55 const {
56 id,
57 travels,
58 waitingPassengers,
59 __typename,
60 administrators,
61 passengers,
62 ...input
63 } = data;
64 await updateEvent({
65 variables: {
66 uuid,
67 eventUpdate: {
68 ...input,
69 },
70 },
71 refetchQueries: ['eventByUUID'],
72 });
73 setIsEditing(false);
74 } catch (error) {
75 console.error(error);
76 addToast(t('event.errors.cant_update'));
77 }
78 };
79
80 const modifyButton = isEditing ? (
81 <Tooltip
82 title={t('event.details.save')}
83 sx={{
84 position: 'absolute',
85 top: theme.spacing(2),
86 right: theme.spacing(2),
87 }}
88 >
89 <IconButton color="primary" onClick={onSave}>
90 <CheckCircleOutlineIcon />
91 </IconButton>
92 </Tooltip>
93 ) : (
94 <Tooltip
95 title={t('event.details.modify')}
96 sx={{
97 position: 'absolute',
98 top: theme.spacing(2),
99 right: theme.spacing(2),
100 }}
101 >
102 <IconButton color="primary" onClick={() => setIsEditing(true)}>
103 <TuneIcon />
104 </IconButton>
105 </Tooltip>
106 );
107
108 return (
109 <Box
110 sx={{
111 position: 'relative',
112 }}
113 >
114 <Container
115 sx={{
116 p: 4,
117 mt: 6,
118 mb: 11,
119 mx: 0,
120 [theme.breakpoints.down('md')]: {
121 p: 2,
122 },
123 }}
124 >
125 <Card
126 sx={{
127 position: 'relative',
128 maxWidth: '100%',
129 width: '480px',
130 p: 2,
131 }}
132 >
133 <Typography variant="h4" pb={2}>
134 {t('event.details')}
135 </Typography>
136 {canEditEventDetails() && modifyButton}
137 {(isEditing || event.name) && <Box pt={2} pr={1.5}>
138 <Typography variant="overline">{t('event.fields.name')}</Typography>
139 <Typography>
140 {isEditing ? (
141 <TextField
142 size="small"
143 fullWidth
144 value={event.name}
145 onChange={e => setEventUpdate({name: e.target.value})}
146 name="name"
147 id="EditEventName"
148 />
149 ) : (
150 <Typography id="EventName">
151 {event.name}
152 </Typography>
153 )}
154 </Typography>
155 </Box>}
156 {(isEditing || event.address) && <Box pt={2} pr={1.5}>
157 <Typography variant="overline">{t('event.fields.date')}</Typography>
158 {isEditing ? (
159 <Typography>
160 <DatePicker
161 slotProps={{
162 textField: {
163 size: 'small',
164 id: `EditEventDate`,
165 fullWidth: true,
166 placeholder: t('event.fields.date_placeholder'),
167 },
168 }}
169 format="DD/MM/YYYY"
170 value={moment(event.date)}
171 onChange={date =>
172 setEventUpdate({
173 date: !date ? null : moment(date).format('YYYY-MM-DD'),
174 })
175 }
176 />
177 </Typography>
178 ) : (
179 <Box position="relative">
180 <Typography id="EventDate">
181 {moment(event.date).format('DD/MM/YYYY')}
182 </Typography>
183 </Box>
184 )}
185 </Box>}
186 {(isEditing || event.address) && (
187 <Box pt={2} pr={1.5}>
188 <Typography variant="overline">
189 {t('event.fields.address')}
190 </Typography>
191 {isEditing ? (
192 <PlaceInput
193 place={event.address}
194 latitude={event.latitude}
195 longitude={event.longitude}
196 onSelect={({place, latitude, longitude}) =>
197 setEventUpdate({
198 address: place,
199 latitude,
200 longitude,
201 })
202 }
203 />
204 ) : (
205 <Box position="relative">
206 <Typography id="EventAddress" sx={{pr: 3}}>
207 <Link
208 target="_blank"
209 rel="noreferrer"
210 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
211 event.address
212 )}`}
213 onClick={e => e.preventDefault}
214 >
215 {event.address}
216 </Link>
217 </Typography>
218 </Box>
219 )}
220 </Box>
221 )}
222 {(isEditing || event.description) && (
223 <Box pt={2} pr={1.5}>
224 <Typography variant="overline">
225 {t('event.fields.description')}
226 </Typography>
227 {isEditing ? (
228 <Typography>
229 <TextField
230 fullWidth
231 multiline
232 maxRows={4}
233 inputProps={{maxLength: 250}}
234 value={event.description || ''}
235 onChange={e =>
236 setEventUpdate({description: e.target.value})
237 }
238 id={`EditEventDescription`}
239 name="description"
240 />
241 </Typography>
242 ) : (
243 <Typography id="EventDescription" sx={{pr: 3}}>
244 {event.description}
245 </Typography>
246 )}
247 </Box>
248 )}
249 {(isEditing || event.lang) && (
250 <Box pt={2} pr={1.5}>
251 <Typography variant="overline">
252 {t('event.fields.lang')}
253 </Typography>
254 {isEditing ? (
255 <LangSelector
256 value={event.lang}
257 onChange={lang => setEventUpdate({lang})}
258 />
259 ) : (
260 <Typography id="EventLang" sx={{pr: 3}}>
261 {t(`PROTECTED.languages.${event.lang}`)}
262 </Typography>
263 )}
264 </Box>
265 )}
266 {!isEditing && (
267 <ShareEvent
268 title={`Caroster ${event.name}`}
269 sx={{width: '100%', mt: 2}}
270 />
271 )}
272 </Card>
273 </Container>
274 </Box>
275 );
276};
277
278export const getServerSideProps = pageUtils.getServerSideProps(
279 async (context, apolloClient) => {
280 const {uuid} = context.query;
281 const {host = ''} = context.req.headers;
282 let event = null;
283
284 // Fetch event
285 try {
286 const {data} = await apolloClient.query({
287 query: EventByUuidDocument,
288 variables: {uuid},
289 });
290 event = data?.eventByUUID?.data;
291 } catch (error) {
292 return {
293 notFound: true,
294 };
295 }
296
297 return {
298 props: {
299 eventUUID: uuid,
300 metas: {
301 title: event?.attributes?.name || '',
302 url: `https://${host}${context.resolvedUrl}`,
303 },
304 },
305 };
306 }
307);
308export default Page;